﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GKS.DataTypes.AI.MissionariesAndCannibals {
    
    /// <summary>
    /// A class that represents a state in the Missionaries and Cannibals game.
    /// </summary>
    public class MissionariesAndCannibalsState {
        #region variables
        private int missionariesOnTheLeft;
        private int missionariesOnTheRight;
        private int cannibalsOnTheLeft;
        private int cannibalsOnTheRight;
        #endregion 

        #region properties
        /// <summary>
        /// Get the number of missionaries on the left shore of the river.
        /// </summary>
        public int MissionariesOnTheLeft {
            get { return missionariesOnTheLeft; }
            private set {
                if (value < 0) { throw new InvalidOperationException("The number of missionaries on the left shore can't be smaller than 0."); }

                missionariesOnTheLeft = value;
            }
        }      

        /// <summary>
        /// Get the number of missionaries on the right shore of the river.
        /// </summary>
        public int MissionariesOnTheRight {
            get { return missionariesOnTheRight; }
            private set {
                if (value < 0) { throw new InvalidOperationException("The number of missionaries on the right shore can't be smaller than 0."); }

                missionariesOnTheRight = value;
            }
        }

        /// <summary>
        /// Get the number of cannibals on the left shore of the river.
        /// </summary>
        public int CannibalsOnTheLeft {
            get { return cannibalsOnTheLeft; }
            private set {
                if (value < 0) { throw new InvalidOperationException("The number of cannibals on the left shore can't be smaller than 0."); }

                cannibalsOnTheLeft = value;
            }
        }

        /// <summary>
        /// Get the number of cannibals on the right shore of the river.
        /// </summary>
        public int CannibalsOnTheRight {
            get { return cannibalsOnTheRight; }
            private set {
                if (value < 0) { throw new InvalidOperationException("The number of cannibals on the right shore can't be smaller than 0."); }

                cannibalsOnTheRight = value;
            }
        }

        /// <summary>
        /// A flag that indicates if the boat is on the left shore or not.
        /// </summary>
        /// <remarks>Only the GET property is available.</remarks>
        public bool IsBoatOnTheLeft { get; private set; }
        #endregion

        #region constructors
        /// <summary>
        /// A constructor of a MissionariesAndCannibalsState.
        /// </summary>
        /// <param name="ml">The number of missionaries on the left shore of the river.</param>
        /// <param name="cl">The number of cannibals on the left shore of the river.</param>
        /// <param name="mr">The number of missionaries on the left shore of the river.</param>
        /// <param name="cr">The number of cannibals on the right shore of the river.</param>
        /// <param name="bl">The boatOnTheLeft flag (TRUE if boat is on the left, FALSE otherwise).</param>
        /// <remarks>
        /// If any of the parameters contain a non valid value an ArgumentException is thrown.
        /// The numerical values must be greater than or equal to 0.
        /// The boat can be on the left (right) shore only if there are people on the left (right) shore.
        /// </remarks>
        /// <exception cref="ArgumentException">Exception thrown if the parameters contain an invalid value.</exception>
        public MissionariesAndCannibalsState(int ml, int cl, int mr, int cr, bool bl) {
            if (ml == 0 && cl == 0 && bl) { throw new ArgumentException("The boat can't be on the left shore if there are no people there."); }
            if (mr == 0 && cr == 0 && !bl) { throw new ArgumentException("The boat can't be on the right shore if there are no people there."); }

            try {
                MissionariesOnTheLeft = ml;
                MissionariesOnTheRight = mr;
                CannibalsOnTheLeft = cl;
                CannibalsOnTheRight = cr;
                IsBoatOnTheLeft = bl;
            } catch (InvalidOperationException ioe) {
                throw new ArgumentException(ioe.Message);
            }
        }

        /// <summary>
        /// A constructor of a MissinariesAndCannibalsState.
        /// </summary>
        /// <param name="stringRepresentation">The string representation of the state.</param>
        /// <remarks>
        /// The format of the parameter must be that of the representation string returned by the ToString() method.
        /// </remarks>
        /// <exception cref="NotImplementedException">Thrown by defualt (the constructor has not been implemented yet).</exception>
        public MissionariesAndCannibalsState(string stringRepresentation) {
            throw new NotImplementedException("This constructor has not been implemented yet.");
        }
        #endregion

        #region methods
        /// <summary>
        /// A method that checks if this state and the specified other state are equals.
        /// </summary>
        /// <param name="theOtherState">The other state that must be compared to this state.</param>
        /// <returns>TRUE if the two states are equals. FALSE otherwise.</returns>
        public bool Equals(MissionariesAndCannibalsState theOtherState) {
            return this.EqualsIgnoreBoat(theOtherState) && this.IsBoatOnTheLeft == theOtherState.IsBoatOnTheLeft;
        }

        /// <summary>
        /// A method that checks if this state and the specified other state are equals (ignoring the position of the boat).
        /// </summary>
        /// <param name="theOtherState">The other state that must be compared to this state.</param>
        /// <returns>TRUE if the two states are equals (ignoring the position of the boat). FALSE otherwise.</returns>
        public bool EqualsIgnoreBoat(MissionariesAndCannibalsState theOtherState) {
            return this.MissionariesOnTheLeft == theOtherState.MissionariesOnTheLeft
                    && this.MissionariesOnTheRight == theOtherState.MissionariesOnTheRight
                    && this.CannibalsOnTheLeft == theOtherState.CannibalsOnTheLeft
                    && this.CannibalsOnTheRight == theOtherState.CannibalsOnTheRight;
        }

        /// <summary>
        /// A method that returns a string representation of the state.
        /// </summary>
        /// <returns>A string representation of the state.</returns>
        /// <remarks>
        /// The string representation has the following format:
        /// (#missionariesOnLeftShore, #cannibalsOnLeftShore, #missionariesOnRightShore, #cannibalsOnRightShore, sideOfBoat[L|R])
        /// </remarks>
        public string ToString() {
            return string.Format("({0}, {1}, {2}, {3}, {4})", 
                                    MissionariesOnTheLeft, 
                                    CannibalsOnTheLeft, 
                                    MissionariesOnTheRight, 
                                    CannibalsOnTheRight, 
                                    IsBoatOnTheLeft ? "L" : "R");
        }

        /// <summary>
        /// A method that checks if it's possible to move one missionary from the left shore to the right and, 
        /// if so, moves one missionary from the left shore of the river to the right shore.
        /// </summary>
        /// <returns>TRUE if the missionary was moved. FALSE otherwise.</returns>
        public bool TryAndMoveOneMissionaryFromLeftToRight() {
            
            // pre conditions
            if (
                IsBoatOnTheLeft                                     // the boat is on the left shore
                && MissionariesOnTheLeft > 0                        // there is at least one missionary on the left shore
                && (
                    MissionariesOnTheLeft == 1                          // there is exactly 1 missionary on the left shore
                    || MissionariesOnTheLeft > CannibalsOnTheLeft       // the number of missionaries on the left shore is greater than the number of cannibals
                ) 
                && MissionariesOnTheRight > CannibalsOnTheRight     // the number of missionaries on the right shore is greater than the number of cannibals
            ) {
                // actions
                MissionariesOnTheLeft--;
                MissionariesOnTheRight++;
                IsBoatOnTheLeft = false;

                return true;
            }
            
            return false;
        }

        /// <summary>
        /// A method that checks if it's possible to move two missionaries from the left shore to the right and, 
        /// if so, moves two missionaries from the left shore of the river to the right shore.
        /// </summary>
        /// <returns>TRUE if the missionaries were moved. FALSE otherwise.</returns>
        public bool TryAndMoveTwoMissionariesFromLeftToRight() {
            // pre conditions
            if (
                IsBoatOnTheLeft                                         // the boat is on the left shore
                && MissionariesOnTheLeft > 1                            // there are at least two missionaries on the left shore
                && MissionariesOnTheRight > CannibalsOnTheRight + 1     // the number of missionaries on the right shore is greater than the number of cannibals + 1
                && (
                    MissionariesOnTheLeft == 2                              // there are exactly 2 missionaries on the left shore
                    || MissionariesOnTheLeft > CannibalsOnTheLeft + 1       // the number of missionaries on the left shore is greater than the number of cannibals + 1
                )
            ) {
                // actions
                MissionariesOnTheLeft -= 2;
                MissionariesOnTheRight += 2;
                IsBoatOnTheLeft = false;

                return true;
            }

            return false;
        }

        /// <summary>
        /// A method that checks if it's possible to move one cannibal from the left shore to the right and, 
        /// if so, moves one cannibal from the left shore of the river to the right shore.
        /// </summary>
        /// <returns>TRUE if the cannibal was moved. FALSE otherwise.</returns>
        public bool TryAndMoveOneCannibalFromLeftToRight() {

            // pre conditions
            if (
                IsBoatOnTheLeft                                 // the boat is on the left shore 
                && CannibalsOnTheLeft > 0                       // there's at least one cannibal on the left shore
                && (
                    MissionariesOnTheLeft > CannibalsOnTheLeft      // the number of missionaries on the left shore is greater than the number of cannibals
                    || MissionariesOnTheLeft == 0                   // there is no missionary on the left shore
                )
            ) {
                // actions to do
                CannibalsOnTheLeft--;
                CannibalsOnTheRight++;
                IsBoatOnTheLeft = false;

                return true;
            }

            return false;
        }

        /// <summary>
        /// A method that checks if it's possible to move two cannibals from the left shore to the right and, 
        /// if so, moves one missionary from the left shore of the river to the right shore.
        /// </summary>
        /// <returns>TRUE if the two cannibals were moved. FALSE otherwise.</returns>
        public bool TryAndMoveTwoCannibalsFromLeftToRight() {

            // pre conditions
            if(
                IsBoatOnTheLeft                                         // the boat is on the left shore
                && CannibalsOnTheLeft > 1                               // there are at least 2 cannibals
                && (
                    MissionariesOnTheRight > CannibalsOnTheRight + 1        // the number of missionaries on the right shore is greater than or equals to the number of cannibals + 2
                    || MissionariesOnTheRight == 0                          // there are no missionaries on the right shore
                )
            ){
                // actions
                CannibalsOnTheLeft -= 2;
                CannibalsOnTheRight += 2;
                IsBoatOnTheLeft = false;

                return true;
            }

            return false;
        }

        /// <summary>
        /// A method that checks if it's possible to move one missionary and one cannibal from the left shore to the right and, 
        /// if so, moves one missionary and one cannibal from the left shore of the river to the right shore.
        /// </summary>
        /// <returns>TRUE if the missionary was moved. FALSE otherwise.</returns>
        public bool TryAndMoveOneMissionaryAndOneCannibalFromLeftToRight() {
            // pre conditions
            if (
                IsBoatOnTheLeft
                && MissionariesOnTheLeft > 0
                && CannibalsOnTheLeft > 0
                && MissionariesOnTheRight >= CannibalsOnTheRight
            ) {
                // actions
                MissionariesOnTheLeft--;
                CannibalsOnTheLeft--;
                MissionariesOnTheRight++;
                CannibalsOnTheRight++;
                IsBoatOnTheLeft = false;

                return true;
            }

            return false;
        }

        /// <summary>
        /// A method that checks if it's possible to move one missionary from the right shore to the left and, 
        /// if so, moves one missionary from the right shore of the river to the left shore.
        /// </summary>
        /// <returns>TRUE if the missionary was moved. FALSE otherwise.</returns>
        public bool TryAndMoveOneMissionaryFromRightToLeft() {
            
            // pre conditions
            if (
                !IsBoatOnTheLeft                                    // the boat is on the right shore
                && MissionariesOnTheRight > 0                       // there is at least one missionary on the right shore
                && MissionariesOnTheLeft > CannibalsOnTheLeft       // the number of missionaries on the left shore is greater than the number of cannibals
                && (
                    MissionariesOnTheRight > CannibalsOnTheRight        // the number of missionaries on the right shore is greater than the number of cannibals
                    || MissionariesOnTheRight == 1                      // there is exactly 1 missionary on the right shore
                )
            ) {
                // actions
                MissionariesOnTheRight--;
                MissionariesOnTheLeft++;
                IsBoatOnTheLeft = true;

                return true;
            }

            return false;
        }

        /// <summary>
        /// A method that checks if it's possible to move two missionaries from the right shore to the left and, 
        /// if so, moves one missionary from the right shore of the river to the left shore.
        /// </summary>
        /// <returns>TRUE if the missionary was moved. FALSE otherwise.</returns>
        public bool TryAndMoveTwoMissionariesFromRightToLeft() {
            // pre conditions
            if (
                !IsBoatOnTheLeft                                        // the boat is on the right shore
                && MissionariesOnTheRight > 1                           // there are at least 2 missionaries on the right shore
                && MissionariesOnTheLeft > CannibalsOnTheLeft + 1       // the number of missionaries on the left shore is greater than the number of cannibals + 1
                && (
                    MissionariesOnTheRight > CannibalsOnTheRight + 1        // the number of missionaries on the right shore is greater than the number of cannibals + 1
                    || MissionariesOnTheRight == 2                          // there are exactly 2 missionaries on the right shore
                )
            ) {
                // actions
                MissionariesOnTheRight -= 2;
                MissionariesOnTheLeft += 2;
                IsBoatOnTheLeft = true;

                return true;
            }
            
            return false;
        }

        /// <summary>
        /// A method that checks if it's possible to move one cannibal from the right shore to the left and, 
        /// if so, moves one cannibal from the right shore of the river to the left shore.
        /// </summary>
        /// <returns>TRUE if the cannibal was moved. FALSE otherwise.</returns>
        public bool TryAndMoveOneCannibalFromRightToLeft() {

            // pre conditions
            if(
                !IsBoatOnTheLeft                                    // the boat is on the right
                && CannibalsOnTheRight > 0                          // there is at least one cannibal on the right shore
                && (
                    MissionariesOnTheLeft > CannibalsOnTheLeft          // the number of missionaries on the left shore is greater than the number of cannibals
                    || MissionariesOnTheLeft == 0                       // there is no missionary on the left shore
                )
            ){
                // the actions to take
                CannibalsOnTheRight--;
                CannibalsOnTheLeft++;
                IsBoatOnTheLeft = true;

                return true;
            }

            return false;
        }

        /// <summary>
        /// A method that checks if it's possible to move two cannibals from the right shore to the left and, 
        /// if so, moves two cannibals from the right shore of the river to the left shore.
        /// </summary>
        /// <returns>TRUE if the cannibals were moved. FALSE otherwise.</returns>
        public bool TryAndMoveTwoCannibalsFromRightToLeft() {

            // pre conditions
            if(
                !IsBoatOnTheLeft                                    // the boat is on the left shore
                && CannibalsOnTheRight > 1                          // there are at least two cannibals on the right shore
                && (
                    MissionariesOnTheLeft > CannibalsOnTheLeft + 1      // there are at least |CannibalsOnTheLeft| + 2 missionaries on the left shore
                    || MissionariesOnTheLeft == 0                       // there are no missionaries on the left shore
                )
            ) {
                // actions
                CannibalsOnTheRight -= 2;
                CannibalsOnTheLeft += 2;
                IsBoatOnTheLeft = true;

                return true;
            }

            return false;
        }

        /// <summary>
        /// A method that checks if it's possible to move one missionary and one cannibal from the right shore to the left and, 
        /// if so, moves one missionary and one cannibal from the right shore of the river to the left shore.
        /// </summary>
        /// <returns>TRUE if the missionary and the cannibal were moved. FALSE otherwise.</returns>
        public bool TryAndMoveOneMissionaryAndOneCannibalFromRightToLeft() {
            // pre conditions
            if (
                !IsBoatOnTheLeft
                && MissionariesOnTheRight > 0
                && CannibalsOnTheRight > 0
                && MissionariesOnTheLeft >= CannibalsOnTheLeft
            ) {
                // actions
                MissionariesOnTheRight--;
                CannibalsOnTheRight--;
                MissionariesOnTheLeft++;
                CannibalsOnTheLeft++;
                IsBoatOnTheLeft = true;

                return true;
            }

            return false;
        }
        #endregion
    }
}
